package util.methods;

import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Vector;

public class StringHelpers 
{
    public static boolean haveSubStringAt ( String str, int nStart, String strSub )
    {
        int nEnd = nStart + strSub.length() - 1;
        if ( nStart < 0 || nEnd >= str.length() )
            return false;
        
        for ( int j = nStart, i = 0; j <= nEnd; ++j, ++i )
        {
            if ( str.charAt(j) != strSub.charAt(i) )
                return false;
        }
        
        return true;
    }
    
    
    // Null safe compare
    public static boolean compareStrings ( String a, String b )
    {
        if ( a == null || b == null )
            return a == b;
        else
            return a.equals( b );
    }
    
    static public String upcaseFirst ( String str )
    {
        // Verify that this actually looks like a word
        if ( isEmpty( str ) || 
                !Character.isLetter( str.charAt(0) ) )
            return str;
        
        String strOut = "";
        if ( str.length() > 1 )
            strOut = str.substring(1);
        return Character.toUpperCase( str.charAt(0) ) + strOut;
    }
    
    static public String escapeQuotes ( String str )
    {
        // replaceAll doesn't seem to work with escaped quotes     
        String strRemain = str;
        String strOut = "";
        while ( strRemain.length() > 0 )
        {
            int n = strRemain.indexOf("\"");
            if ( n >= 0 )
            {
                strOut += strRemain.substring( 0, n );
                strOut += "\\\"";
                if ( ( n + 1 ) < strRemain.length() )
                {
                    strRemain = strRemain.substring( n + 1 );
                }
                else
                    strRemain = "";
            }
            else
            {
                strOut += strRemain;
                strRemain = "";
            }
        }
        return strOut;
    }
    
    // Adds a word to the input line.  If the line is not empty, a space is
    // added before the word
    static public String addWord ( String strWord, String strAddTo )
    {
        // Fun with boundary conditions (aka paranoia)
        String str = strAddTo;
        if ( isEmpty ( strAddTo ) )
            str = "";
        else
            if ( !isEmpty ( strWord ) )
            {
                str += " ";
            }
            
        if ( !isEmpty ( strWord ) ) 
            str += strWord;
        
        return str;
    }   
    
    static private boolean isWordDelimiter ( char ch )
    {
        return Character.isWhitespace( ch ) || 
                ch == '.' || 
                ch == '\"' || 
                ch == '\'' ||
                ch == '(' || 
                ch == ')';
    }
    
    static public boolean containsWord ( String str, String strSubStr )
    {
        // Look for the sub-string
        int n = str.indexOf( strSubStr );
        if ( n < 0 )
            return false;
        
        // If found make sure the word is somehow delimited
        if ( n == 0 || isWordDelimiter( str.charAt(n-1) ) /* left side */  )
        {
            int nAfterIdx = n + strSubStr.length();
            return nAfterIdx == str.length() || isWordDelimiter( str.charAt(nAfterIdx) ); /* right side */
        }
        
        return false;
    }
    
    // A "smart" split that will only split between words.  Splits the input 
    // sentance into lines of at most the input number of characters
    static public Vector<String> splitTextIntoLines ( String str, int nMax )
    {
        Vector<String> out = new Vector<String> ();
        String strLine = "";
        StringTokenizer toker = new StringTokenizer ( str, ",;:\"(){}[]<> ", true );
        boolean bLastWasLeftParen = false;
        String strCurToken = null;
        
        while ( toker.hasMoreTokens() || strCurToken != null )
        {
            strLine = "";
            bLastWasLeftParen = false;
            
            // Build up the current line
            while ( toker.hasMoreTokens() || strCurToken != null )
            {
                while ( strCurToken == null && toker.hasMoreTokens() )
                {
                    strCurToken = toker.nextToken().trim();
                    if ( StringHelpers.isEmpty(strCurToken) )
                        strCurToken = null;
                }
                
                if ( strCurToken == null )
                    break;
                
                if ( ( strCurToken.length() + strLine.length() ) <= nMax || strCurToken.length() > nMax )
                {
                    if ( strLine.length() > 0 && !isPunctuation ( strCurToken ) && !bLastWasLeftParen )
                        strLine += " ";
                    bLastWasLeftParen = strCurToken.equals( "(" );
                    strLine += strCurToken;
                    strCurToken = null;
                }
                else
                    break; // Line is full
            }
            
            out.add( strLine );
        }
        
        return out;
    }
    
    static public boolean isPunctuation ( String str )
    {
        return str.equals( "." ) || 
                    str.equals( "," ) || 
                    str.equals( ":" ) || 
                    str.equals( ";" ) || 
                    str.equals( "\"" ) || 
                    str.equals( "(" ) || 
                    str.equals( ")" ) ||
                    str.equals( "{" ) ||
                    str.equals( "}" ) ||
                    str.equals( "[" ) ||
                    str.equals( "]" ) ||
                    str.equals( "<" ) ||
                    str.equals( ">" );
    }
    
    static public Vector<String> split ( String str, String strDelims )
    {
        if ( str == null )
            return null;
        
        Vector<String> list = new Vector<String> ();
        StringTokenizer toker = new StringTokenizer ( str, strDelims );
        while ( toker.hasMoreTokens() )
            list.add ( toker.nextToken() ); 
        
        return list;
    }
    
    public static String join ( Object [] theList, String strDelim )
    {
        String str = "";
        for ( int i = 0; i < theList.length; ++i )
        {
            if ( !StringHelpers.isEmpty(str) )
                str += strDelim;
            str += theList[i].toString();
        }
        return str;
    }
    
    public static <T> String join ( Iterator<T> iter, String strDelim )
    {
        String str = "";
        while ( iter.hasNext() )
        {
            if ( !StringHelpers.isEmpty(str) )
                str += strDelim;
            str += iter.next().toString();
        }
        return str;
    }
    
    static public boolean isEmpty ( String str )
    {
        return str == null || str.length() == 0;
    }
    
    static public boolean equalsNoCase ( String strA, String strB ) 
    {
        // If either is null, return true iff both are null
        if ( strA == null || strB == null )
            return strA == strB;
        
        // Otherwise check matching lengths
        boolean bAllMatch = strA.length() == strB.length();
                
        // All other things being equal, compare the characters
        char chA, chB;
        for ( int j = 0; j < strA.length() && bAllMatch; ++j )
        {
            chA = Character.toUpperCase( strA.charAt(j) );
            chB = Character.toUpperCase( strB.charAt(j) );
            bAllMatch = chA == chB;
        }
        
        return bAllMatch;
    }
    
    static public int indexOfNoCase ( String str, String strSubStr )
    {
        return indexOfNoCase ( str, strSubStr, 0 ); 
    }
    
    static public int indexOfNoCase ( String str, String strSubStr, int nStart )
    {
        int j;
        int nSubLen = strSubStr.length();
        boolean bAllMatch = true;
        char chA, chB;
        
        for ( int i = nStart; i < (str.length() - nSubLen + 1); ++i )
        {
            bAllMatch = true;
            for ( j = 0; j < nSubLen && bAllMatch; ++j )
            {
                chA = Character.toUpperCase( strSubStr.charAt(j) );
                chB = Character.toUpperCase( str.charAt(i+j) );
                bAllMatch = chA == chB;
            }
                
            if ( bAllMatch )
                return i;
        }
            
        return -1;
    }
    
    static public String padLeftTo ( String str, char chPadWith, int nDesLength )
    {
        int nAdd = nDesLength - str.length();
        if ( nAdd > 0 )
        {
            for ( int i = 0; i < nAdd; ++i )
                str = chPadWith + str;
        }
            
        return str;
    }
    
    // Normalize the input URL (the Java URL class has
    // dumb/rigid parsing expecting either "http://host_name/path_to_file" or
    // "http:relative_path_to_file")
    static public String normalizeURL ( String str )
    {
        boolean bHadFile = false;
        
        // Check for null
        if ( str == null )
            return null;
        
        // Normalize the URL string (parse off "html:" and any quotes)
        String strURL = StringHelpers.stripQuotes( str.trim() ).trim();
        if ( strURL.startsWith( "http:" ) )
            strURL = strURL.substring( 5 ).trim();  
        else
            if ( strURL.startsWith( "file:" ) )
            {
                bHadFile = true;
                strURL = strURL.substring( 5 ).trim();  
            }           
            else
                if ( strURL.startsWith( "/" ) )
                {
                    bHadFile = true;
                }
        
        // If there is no preceding // look for .com, .org, .net, etc
        if ( !strURL.startsWith ("/") && strURL.length() > 4 )
        {
            int nSlashIdx = strURL.indexOf( "/" );
            if ( nSlashIdx < 0 )
                nSlashIdx = strURL.length();
            
            int nDotIdx = strURL.lastIndexOf( ".", nSlashIdx );
            int nTopLevelDomainLen = nSlashIdx - nDotIdx - 1;
            
            if ( nDotIdx > 0 && ( nTopLevelDomainLen >= 2 && nTopLevelDomainLen <= 4 ) )
            {
                String strTLD = strURL.substring( nDotIdx + 1, nSlashIdx );
                if ( isTopLevelDomain ( strTLD ) )
                {
                    // This is almost certainly a host name, so insert the //
                    strURL = "//" + strURL;
                }
            }
        }
        
        // Normalize starting slashes:
        if ( strURL.startsWith ("/") && !strURL.startsWith ("//") )
            strURL = "/" + strURL;
        if ( strURL.startsWith ("//") && !strURL.startsWith ("///") && bHadFile )
            strURL = "/" + strURL;
    
        // (Re)insert "html:"
        if ( StringHelpers.isEmpty( strURL ) )
            return ""; // There's nothing worthwile in the string, consider null
        else
            if ( bHadFile )
                return "file:" + strURL;
            else
                return "http:" + strURL;
    }
    
    static boolean isTopLevelDomain ( String str )
    {
        if ( str == null || str.length() < 2 || str.length() > 4 )
            return false;
        
        str = str.toUpperCase();
        
        /* 3 and 4 letter top-level domains
                # COM = US Commercial
                # EDU = US Educational
                # GOV = US Government
                # INT = International
                # MIL = US Military
                # NET = Network
                # ORG = Non-Profit Organization
                # ARPA = Arpanet
                # NATO = Nato field     
        */
        
        if ( str.length() >= 3 )
            return str.equals( "COM") || str.equals( "EDU") || str.equals( "GOV") || str.equals( "INT") ||
                    str.equals( "MIL") || str.equals( "NET") || str.equals( "ORG") || str.equals( "ARPA") ||
                     str.equals( "NATO");       
        else
            /* There are some many 2 letter combinations, just make sure it isn't a two letter file extension.
             * I found thest at: http://www.matisse.net/files/formats.html */
            return !str.equals( "GZ" ) &&  // .z: gzip 
                    !str.equals( "HA" ) && // (improved PPMC - 4th order Markov modeling)
                    !str.equals( "PP" ) && // .pp: PowerPacker (Amiga)
                    !str.equals( "QT" ) && // QuickTime Movie
                    !str.equals( "PS" ) && // Postscript
                    !str.equals( "UU" ); // A uuencoded file        
    }
    
    static String stripQuotes ( String str )
    {
        if ( str.length() >= 2 &&
                (str.charAt(0) == '\'' || str.charAt(0) == '\"') &&
                    (str.charAt(str.length()-1) == '\'' || str.charAt(str.length()-1) == '\"' ) )
            return str.substring( 1, str.length()-1 );
        else
            return str;
    }
    
    public static String removeComment(String s) {
    	if (s != null && !s.equals(""))
    		return s.split("#")[0].trim();
    	return s;
    }
    
    private StringHelpers () {}; // Don't instantiate
}
